﻿using System;
using System.Collections.Generic;

using System.Text;
using RoughJs.Error;

namespace RoughJs.Ast
{
    /// <summary>
    /// 2010-01-5 ----- 2010-01-29
    /// 陈曦 1.0
    /// Microsoft Public License (Ms-PL)
    ///This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
    ///1. Definitions
    ///The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
    ///A "contribution" is the original software, or any additions or changes to the software.
    ///A "contributor" is any person that distributes its contribution under this license.
    ///"Licensed patents" are a contributor's patent claims that read directly on its contribution.
    ///2. Grant of Rights
    ///(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
    ///(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
    ///3. Conditions and Limitations
    ///(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
    ///(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
    ///(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
    ///(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
    ///(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. 
    /// </summary>
    public class NativeGenerator : IdScriptableObject
    {
        private const int
     Id_close = 1,
     Id_next = 2,
     Id_send = 3,
     Id_throw = 4,
     Id___iterator__ = 5,
     MAX_PROTOTYPE_ID = 5;

        private static Object GENERATOR_TAG = "Generator";

        private NativeFunction function;
        private Object savedState;
        private String lineSource;
        private int lineNumber;
        private bool firstTime = true;
        private bool locked;

        public class GeneratorClosedException : Exception
        {
            private static long serialVersionUID = 2561315658662379681L;
        }


        public static NativeGenerator init(ScriptableObject scope, bool sealed1)
        {
            // Generator
            // Can't use "NativeGenerator().exportAsJSClass" since we don't want
            // to define "Generator" as a constructor in the top-level scope.

            NativeGenerator prototype = new NativeGenerator();
            if (scope != null)
            {
                prototype.setParentScope(scope);
                prototype.setPrototype(getObjectPrototype(scope));
            }
            prototype.activatePrototypeMap(MAX_PROTOTYPE_ID);
            if (sealed1)
            {
                prototype.sealObject();
            }

            // Need to access Generator prototype when constructing
            // Generator instances, but don't have a generator constructor
            // to use to find the prototype. Use the "associateValue"
            // approach instead.
            if (scope != null)
            {
                scope.associateValue(GENERATOR_TAG, prototype);
            }

            return prototype;
        }

        /**
         * Only for constructing the prototype object.
         */
        private NativeGenerator() { }

        public NativeGenerator(Scriptable scope, NativeFunction function,
                               Object savedState)
        {
            this.function = function;
            this.savedState = savedState;
            // Set parent and prototype properties. Since we don't have a
            // "Generator" constructor in the top scope, we stash the
            // prototype in the top scope's associated value.
            Scriptable top = ScriptableObject.getTopLevelScope(scope);
            this.setParentScope(top);
            NativeGenerator prototype = (NativeGenerator)
                ScriptableObject.getTopScopeValue(top, GENERATOR_TAG);
            this.setPrototype(prototype);
        }

        public static int GENERATOR_SEND = 0,
                                GENERATOR_THROW = 1,
                                GENERATOR_CLOSE = 2;


        public override String getClassName()
        {
            return "Generator";
        }

        public class Callable1 : Callable
        {

            #region Callable Members

            public object call(Context cx, Scriptable scope,
                                               Scriptable thisObj, Object[] args)
            {
                return ((NativeGenerator)thisObj).resume(cx, scope,
                                       GENERATOR_CLOSE, new GeneratorClosedException());
            }

            #endregion
        }

        /**
         * Close the generator if it is still open.
         */

        public void finalize()
        {
            if (savedState != null)
            {
                // This is a little tricky since we are most likely running in
                // a different thread. We need to get a Context to run this, and
                // we must call "doTopCall" since this will likely be the outermost
                // JavaScript frame on this thread.
                Context cx = Context.getCurrentContext();
                ContextFactory factory = cx != null ? cx.getFactory()
                                                    : ContextFactory.getGlobal();
                factory.call(new CloseGeneratorAction(this));
            }
        }

        private class CloseGeneratorAction : ContextAction
        {
            private NativeGenerator generator;

            public CloseGeneratorAction(NativeGenerator generator)
            {
                this.generator = generator;
            }

            public Object run(Context cx)
            {
                Scriptable scope = ScriptableObject.getTopLevelScope(generator);
                Callable closeGenerator = new Callable1();
                return ScriptRuntime.doTopCall(closeGenerator, cx, scope,
                                               generator, null);
            }
        }



        public override void initPrototypeId(int id)
        {
            String s;
            int arity;
            switch (id)
            {
                case Id_close: arity = 1; s = "close"; break;
                case Id_next: arity = 1; s = "next"; break;
                case Id_send: arity = 0; s = "send"; break;
                case Id_throw: arity = 0; s = "throw"; break;
                case Id___iterator__: arity = 1; s = "__iterator__"; break;
                default: throw new ArgumentException(id.ToString());
            }
            initPrototypeMethod(GENERATOR_TAG, id, s, arity);
        }


        public override Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
                                 Scriptable thisObj, Object[] args)
        {
            if (!f.hasTag(GENERATOR_TAG))
            {
                return base.execIdCall(f, cx, scope, thisObj, args);
            }
            int id = f.methodId();

            if (!(thisObj is NativeGenerator))
                throw incompatibleCallError(f);

            NativeGenerator generator = (NativeGenerator)thisObj;

            switch (id)
            {

                case Id_close:
                    // need to run any pending finally clauses
                    return generator.resume(cx, scope, GENERATOR_CLOSE,
                                            new GeneratorClosedException());

                case Id_next:
                    // arguments to next() are ignored
                    generator.firstTime = false;
                    return generator.resume(cx, scope, GENERATOR_SEND,
                                            Undefined.instance);

                case Id_send:
                    {
                        Object arg = args.Length > 0 ? args[0] : Undefined.instance;
                        if (generator.firstTime && !arg.Equals(Undefined.instance))
                        {
                            throw ScriptRuntime.typeError0("msg.send.newborn");
                        }
                        return generator.resume(cx, scope, GENERATOR_SEND, arg);
                    }

                case Id_throw:
                    return generator.resume(cx, scope, GENERATOR_THROW,
                        args.Length > 0 ? args[0] : Undefined.instance);

                case Id___iterator__:
                    return thisObj;

                default:
                    throw new ArgumentException((id).ToString());
            }
        }

        private Object resume(Context cx, Scriptable scope, int operation,
                              Object value)
        {
            if (savedState == null)
            {
                if (operation == GENERATOR_CLOSE)
                    return Undefined.instance;
                Object thrown;
                if (operation == GENERATOR_THROW)
                {
                    thrown = value;
                }
                else
                {
                    thrown = NativeIterator.getStopIterationObject(scope);
                }
                throw new JavaScriptException(thrown, lineSource, lineNumber);
            }
            try
            {
                lock (this)
                {
                    // generator execution is necessarily single-threaded and
                    // non-reentrant.
                    // See https://bugzilla.mozilla.org/show_bug.cgi?id=349263
                    if (locked)
                        throw ScriptRuntime.typeError0("msg.already.exec.gen");
                    locked = true;
                }
                return function.resumeGenerator(cx, scope, operation, savedState,
                                                value);
            }
            catch (GeneratorClosedException e)
            {
                // On closing a generator in the compile path, the generator
                // throws a special exception. This ensures execution of all pending
                // finalizers and will not get caught by user code.
                return Undefined.instance;
            }
            catch (RhinoException e)
            {
                lineNumber = e.lineNumber();
                lineSource = e.lineSource();
                savedState = null;
                throw e;
            }
            finally
            {
                lock (this)
                {
                    locked = false;
                }
                if (operation == GENERATOR_CLOSE)
                    savedState = null;
            }
        }

        // #string_id_map#

        public override int findPrototypeId(String s)
        {
        int id;
// #generated# Last update: 2007-06-14 13:13:03 EDT
        id = 0; String X = null; int c;
            int s_length = s.Length;
            if (s_length==4) {

                c = s.ToCharArray()[0];
                if (c=='n') { X="next";id=Id_next; }
                else if (c=='s') { X="send";id=Id_send; }
            }
            else if (s_length==5) {
                c = s.ToCharArray()[0];
                if (c=='c') { X="close";id=Id_close; }
                else if (c=='t') { X="throw";id=Id_throw; }
            }
            else if (s_length==12) { X="__iterator__";id=Id___iterator__; }
            if (X!=null && X!=s && !(X==s)) id = 0;
            goto L0;
         L0: ;
// #/generated#
        return id;
    }



        // #/string_id_map#


    }
}
